package cn.jtcoding.springframework.core;

import cn.jtcoding.springframework.core.annotation.Autowired;
import cn.jtcoding.springframework.core.annotation.Qualifier;
import cn.jtcoding.springframework.aop.aspect.AspectWeaver;
import cn.jtcoding.springframework.core.factory.AliasBeanFactory;
import cn.jtcoding.springframework.core.support.AnnotatedBeanDefinition;
import cn.jtcoding.springframework.core.support.BeanDefinition;
import cn.jtcoding.springframework.enums.ScopeEnum;
import cn.jtcoding.springframework.support.CommandLineRunner;
import cn.jtcoding.springframework.support.InitializingBean;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class BeanContainer implements AliasBeanFactory {

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    /** Cache of singleton objects created by FactoryBeans: FactoryBean name to object. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(16);

    private final AnnotationBeanDefinitionScanner annotationBeanDefinitionScanner;
    private final AspectWeaver aspectWeaver;
    private BeanContainer() {
        annotationBeanDefinitionScanner = new AnnotationBeanDefinitionScanner();
        aspectWeaver = new AspectWeaver();
    }

    public static BeanContainer getInstance() {
        return BeanContainerEnum.HOLDER.instance;
    }

    private enum BeanContainerEnum {
        HOLDER;
        private final BeanContainer instance;
        BeanContainerEnum() {
            instance = new BeanContainer();
        }
    }

    public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> clazz) {
        return beanDefinitionMap.values().parallelStream()
                .map(BeanDefinition::getBeanClass)
                .filter(beanClass -> beanClass.isAnnotationPresent(clazz))
                .collect(Collectors.toSet());
    }

    /**
     * IoC 容器实现：依赖查找 + 依赖注入
     * @param clazz 启动类Class
     */
    public void loadBean(Class<?> clazz) {
        dependenceLookupAndInject(clazz);
    }

    @SuppressWarnings("unchecked")
    private void dependenceLookupAndInject(Class<?> clazz) {
        Set<AnnotatedBeanDefinition> beanDefinitions = (Set<AnnotatedBeanDefinition>) annotationBeanDefinitionScanner.scan(clazz);

        beanDefinitions.parallelStream().forEach(beanDefinition -> {
            String beanName = beanDefinition.getBeanClass().getName();
            beanDefinitionMap.put(beanName, beanDefinition);

            String aliasName = getAliasName(beanDefinition.getAnnotations(), beanName);
            setAliasName(beanName, aliasName);
        });

        for (AnnotatedBeanDefinition beanDefinition : beanDefinitions) {
            Class<?> beanClass = beanDefinition.getBeanClass();
            String beanName = beanClass.getName();

            boolean singletonObj = beanDefinition.getScope().equals(ScopeEnum.SINGLETON);
            if (singletonObj && !singletonObjects.containsKey(beanName)) {
                getBean(beanName);
            }
        }
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
        Object instance = null;
        Class<?> beanClass = beanDefinition.getBeanClass();
        boolean singletonObj = beanDefinition.getScope().equals(ScopeEnum.SINGLETON);
        if (singletonObj) {
            instance = singletonObjects.get(beanClass.getName());
            if (instance != null) {
                return instance;
            }
        }
        try {
            instance = beanClass.getDeclaredConstructor().newInstance();
            if (singletonObj) {
                singletonObjects.put(beanClass.getName(), instance);
            }
            populateBean(beanClass, instance);

            // 执行对外提供的接口实现
            if (CommandLineRunner.class.isAssignableFrom(beanClass)) {
                ((CommandLineRunner) instance).run();
            }

            instance = initializingBean(beanClass, instance);
            if (singletonObj) {
                singletonObjects.put(beanClass.getName(), instance);
            }
        }
        catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new IllegalArgumentException("create instance failure. " + beanClass.getName(), e);
        }
        catch (Exception e) {
            throw new RuntimeException("create instance failure. " + beanClass.getName(), e);
        }
        return instance;
    }

    private Object initializingBean(Class<?> beanClass, Object instance) throws Exception {
        invokeInitMethods(beanClass, instance);

        // AOP 生成代理对象
        instance = applyBeanPostProcessorsAfterInitialization(beanClass, instance);
        return instance;
    }

    private void invokeInitMethods(Class<?> beanClass, Object instance) throws Exception {
        if (InitializingBean.class.isAssignableFrom(beanClass)) {
            ((InitializingBean) instance).afterPropertiesSet();
        }
    }

    private Object applyBeanPostProcessorsAfterInitialization(Class<?> targetClass, Object instance) {
        return aspectWeaver.wrapIfNecessary(targetClass, instance);
    }

    /**
     * 属性填充
     */
    private void populateBean(Class<?> beanClass, Object instance) {
        Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            // 自己注入自己
            if (declaredField.getType().getName().equals(beanClass.getName())) {
                declaredField.setAccessible(true);
                try {
                    declaredField.set(instance, instance);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("autowired failure. [" + beanClass.getName() + "]" + e.getMessage());
                }
                continue;
            }

            Autowired autowired = declaredField.getAnnotation(Autowired.class);
            if (autowired == null || !autowired.require()) {
                continue;
            }

            // default by type
            String className = declaredField.getType().getName();
            String beanName = className;

            // custom by name
            Qualifier qualifier = declaredField.getAnnotation(Qualifier.class);
            if (qualifier != null) {
                beanName = qualifier.value();
            } else {
                if (!beanDefinitionMap.containsKey(className)) {
                    throw new IllegalArgumentException("Bean not found. " + className);
                }
            }

            Object fieldInstance = getBean(beanName);
            declaredField.setAccessible(true);
            try {
                declaredField.set(instance, fieldInstance);
            }
            catch (IllegalAccessException e) {
                throw new RuntimeException("autowired failure. [" + className + "]" + e.getMessage());
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getBean(Class<T> clazz) {
        if (!beanDefinitionMap.containsKey(clazz.getName())) {
            return null;
        }
        return (T) resolveBean(clazz.getName());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getBean(String beanName) {
        if (!aliasMap.containsValue(beanName)) {
            return (T) resolveBean(beanName);
        }
        String className = aliasMap.entrySet().parallelStream()
                .filter(entry -> Objects.equals(beanName, entry.getValue()))
                .findFirst()
                .get()
                .getKey();;
        return (T) resolveBean(className);
    }

    private Object resolveBean(String className) {
        if (singletonObjects.containsKey(className)) {
            return singletonObjects.get(className);
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(className);
        if (beanDefinition == null) {
            return null;
        }
        return doCreateBean(beanDefinition);
    }


}
